// xcachetools
//create by kang
//万能缓存清理工具
package main

import (
	"bufio"
	"bytes"
	"color"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"memcache"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/gosexy/redis"
)

const (
	ProxyIp     = "192.168.x.x:1080"
	DILianUrl   = "http://apiaca.dnion.com:8081/report/getContentRefresh.do?"
	DiLianToken = ""
	DLPushUrl   = "http://push.dnion.com/cdnUrlPush.do"
	DLCaptcha   = ""
	WsUrl       = "http://wscp.lxdns.com:8080/wsCP/servlet/contReceiver?"
	WsUser      = ""
	WsPass      = ""
	InmQuery    = "http://192.168.xx.xx:7070/xxx"
)

//for appname
type Serviceinfo struct {
	Agentip     string `json:"agent_tcp_ip"`
	Serviceport int    `json:"service_port"`
}

//for appname ana serviceinfo json
func Jsoninfo(jsstr []byte) []string {
	var sf []Serviceinfo
	var xh []string
	//if err := json.Unmarshal([]byte(jsstr), &sf); err != nil {
	if err := json.Unmarshal(jsstr, &sf); err != nil {
		fmt.Println(err)
	}
	for i := 0; i < len(sf); i++ {
		xh = append(xh, sf[i].Agentip+":"+fmt.Sprintf("%d", sf[i].Serviceport))
	}
	return xh
}

//for domain
type Contentinfo struct {
	Content string `json:"content"`
}

//for domain
func Djsoninfo(jsstr []byte) []string {
	var sf []Contentinfo
	var xh []string
	if err := json.Unmarshal(jsstr, &sf); err != nil {
		fmt.Println(err)
	}
	for i := 0; i < len(sf); i++ {
		xh = append(xh, sf[i].Content)
	}
	return xh
}

func Spiltfunc(s rune) bool {
	if s == '_' || s == ':' {
		return true
	} else {
		return false
	}
}

//数组去重去空
func RemoveDuplicatesAndEmpty(a []string) (ret []string) {
	a_len := len(a)
	for i := 0; i < a_len; i++ {
		if (i > 0 && a[i-1] == a[i]) || len(a[i]) == 0 {
			continue
		}
		ret = append(ret, a[i])
	}
	return
}

func ReadFileList(f string) []string {
	var file *os.File
	var err interface{}
	if f != "" {
		file, err = os.Open(f)
		if err != nil {
			panic(err)
		}
		defer file.Close()
	} else {
		fmt.Println("no found" + f)
	}

	// 建立行式文件缓冲流
	bi := bufio.NewReader(file)
	var list []string
	for {
		line, err := bi.ReadString('\n')
		if err != nil {
			break // 读取完成整个文件退出循环
		}
		str := strings.Replace(line, "\n", "", -1)
		list = append(list, str)
	}
	return list
}

//string to md5
func StrtoMd5(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	x := h.Sum(nil)
	cc := hex.EncodeToString(x)
	return cc
}

// get 请求URL
func ReqUrl(turl, methd, xhost string) (*http.Response, error) {
	request, _ := http.NewRequest(methd, turl, nil)
	request.Header.Set("User-Agent", "PConline-xcachetools")
	request.Host = xhost
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(10 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

//proxy is atsip
func GetByProxy(url_addr, proxy_addr, method string) (*http.Response, error) {
	proxy_addr = "http://" + proxy_addr
	request, _ := http.NewRequest(method, url_addr, nil)
	request.Header.Set("User-Agent", "xcachetools")
	proxy, err := url.Parse(proxy_addr)
	if err != nil {
		return nil, err
		log.Printf("ERR:GetByProxy err,ip:%s,err:\n", proxy_addr, err)
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(5 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

func ClsAts(tsip, turl []string) {
	for _, xurl := range turl {
		for _, ip := range tsip {
			resp, err := GetByProxy(xurl, ip, "PURGE")
			if err != nil {
				log.Printf("ERR: error ip or error port:ip=%s,uri=%s\n", ip, xurl)
				continue
			} else {
				defer resp.Body.Close()
				switch {
				case resp.StatusCode == 200:
					log.Printf("INFO:DelSuccess %s,ip=%s\n", xurl, ip)
				case resp.StatusCode == 404:
					log.Printf("INFO:NotFound %s,ip=%s\n", xurl, ip)
				default:
					log.Printf("WARN:Failure,StatusCode=%d,atsip=%s,uri=%s", resp.StatusCode, ip, xurl)
				}
			}
		}
	}
}

func ClsWangSu(proxyip, cdnintf, pushuser, pushpasswd string, xurl []string) {
	var allurl string
	var allurl1 string
	for _, url := range xurl {
		//uri := strings.TrimLeft(url, "http://")
		uri := strings.Replace(url, "http://", "", 1)
		uri1 := strings.Replace(uri, "&", "%26", -1)
		allurl = allurl + uri + ";"
		allurl1 = allurl1 + uri1 + ";"
	}
	turl := cdnintf + "username=" + pushuser + "&passwd=" + StrtoMd5(pushuser+pushpasswd+strings.TrimRight(allurl, ";")) + "&url=" + strings.TrimRight(allurl1, ";")

	var resp *http.Response
	var err error
	resp, err = GetByProxy(turl, proxyip, "GET")
	if err != nil {
		log.Printf("ERR:call cdn api fail\n")
		return
	}
	defer resp.Body.Close()
	bodyBytes, _ := ioutil.ReadAll(resp.Body)
	bodycontent := string(bodyBytes)
	log.Printf("CDNINFO:%s,StatusCode=%d,uri=%s", bodycontent, resp.StatusCode, turl)

}

//dilian clean CDN cache
func ClsDiLian(proxyip, cdnintf, logintoken string, xurl []string) {
	var allurl string
	for _, url := range xurl {
		//uri := strings.TrimLeft(url, "http://")
		allurl = allurl + url + ","
	}
	turl := cdnintf + strings.TrimRight(allurl, ",") + "&loginToken=" + logintoken

	var resp *http.Response
	var err error
	resp, err = GetByProxy(turl, proxyip, "GET")
	defer resp.Body.Close()
	if err != nil {
		log.Printf("ERR:failure url :%s\n", turl)
		return
	} else {
		bodyBytes, _ := ioutil.ReadAll(resp.Body)
		bodycontent := string(bodyBytes)
		log.Printf("CDNINFO:%s,StatusCode=%d,uri=%s", bodycontent, resp.StatusCode, turl)
	}
}

func ClsNgx(xhost, tturl []string) {
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			ngxurl := "http://" + host + "/purge" + uri
			rep, err1 := ReqUrl(ngxurl, "GET", domain)
			if err1 != nil {
				return
			}
			switch rep.StatusCode {
			case 200:
				log.Printf("INFO:DelSuccess %s,ip=%s\n", turl, host)
			case 404:
				log.Printf("INFO:NotFound %s,ip=%s\n", turl, host)
			default:
				log.Printf("ERR:Failure %s,ip=%s\n", turl, host)
			}
		}
	}

}

//dilian post by proxy
func PostByProxy(url_addr, proxy_addr, method string, body *bytes.Reader) (*http.Response, error) {
	proxy_addr = "http://" + proxy_addr
	request, _ := http.NewRequest(method, url_addr, body)
	request.Header.Set("User-Agent", "xcachetools_daemon")
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
	proxy, err := url.Parse(proxy_addr)
	if err != nil {
		return nil, err
		log.Printf("ERR:GetByProxy err,ip:%s,err:\n", proxy_addr, err)
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
			Dial: func(netw, proxy string) (net.Conn, error) {
				deadline := time.Now().Add(5 * time.Second)
				c, err := net.DialTimeout(netw, proxy, time.Second*5)
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
	return client.Do(request)
}

//DiLian CDN api2
func ClsDiLian2(proxyip, cdnintf, token string, xurl []string) {
	if len(xurl) <= 0 {
		log.Println("cls dilian url is null")
		return
	}
	var allurl string
	for _, url := range xurl {
		allurl = allurl + url + ","
	}
	PostContent := "captcha=" + token + "&type=1&url=" + strings.TrimRight(allurl, ",")
	var res *http.Response
	body := bytes.NewBuffer([]byte(PostContent))
	isproxy := (proxyip != "")
	if isproxy {
		res, _ = PostByProxy(cdnintf, proxyip, "POST", bytes.NewReader([]byte(PostContent)))
	} else {
		res, _ = http.Post(cdnintf, "application/x-www-form-urlencoded; param=value", body)
	}
	result, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println("dilian api error!")
		return
	}
	res.Body.Close()
	if strings.Contains(fmt.Sprintf("%s\n", result), "<code>200") {
		fmt.Printf("CDNINFO:SUCCESS,%s,uri=%s\n", result, allurl)
	} else {
		fmt.Printf("CDNINFO:FAIL,%s,uri=%s\n", result, allurl)
	}
}

func ClsHcs(xhost, tturl []string) {
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			hcsurl := "http://" + host + uri
			rep, err := ReqUrl(hcsurl, "DELETE", domain)
			if err != nil {
				return
			}
			switch rep.StatusCode {
			case 200:
				log.Printf("INFO:DelSuccess %s,ip=%s\n", turl, host)
			case 404:
				log.Printf("INFO:NotFound %s,ip=%s\n", turl, host)
			default:
				log.Printf("ERR:Failure %s,ip=%s\n", turl, host)
			}
		}
	}
}

func ClsMc(xhost, xkey []string) {
	for _, host := range xhost {
		mclient := memcache.New(host)
		xip := strings.Split(host, ":")[0]
		xport := strings.Split(host, ":")[1]
		for _, key := range xkey {
			if key == "flush_all" {
				runout := exec.Command("/bin/bash", "-c", "echo flush_all|nc "+xip+" "+xport)
				buf, err := runout.Output()
				if err != nil {
					log.Printf("INFO:%s,ip=%s,DelResult:%s", key, host, string(buf))
				} else {
					log.Printf("INFO:%s,ip=%s,DelResult:%s", key, host, string(buf))
				}
			} else {
				err := mclient.Delete(key)
				if err != nil {
					log.Printf("INFO:ConnFail or NotFound %s,ip=%s\n", key, host)
				} else {
					log.Printf("INFO:DelSucsse %s,ip=%s\n", key, host)
				}
			}
		}
	}
}

func GetAppIp(key, service string) []string {
	var resp *http.Response
	var err error
	var xxh []string
	if strings.Contains(key, ".") {
		sqlquery := "SELECT content FROM app_map_flash WHERE appId in (SELECT appId FROM app_map_flash WHERE type='domain' AND (content REGEXP '.*," + key + "_.*' OR content REGEXP '^" + key + "_.*')) AND type='service' AND content like '%" + service + "%';"
		resp, err = http.PostForm(InmQuery, url.Values{"sql": {sqlquery}})
		if err != nil {
			fmt.Println(err)
		}
		defer resp.Body.Close()
		body, _ := ioutil.ReadAll(resp.Body)
		for _, a := range Djsoninfo(body) {
			b := strings.Split(a, ",")
			for _, c := range b {
				d := strings.FieldsFunc(c, Spiltfunc)
				xxh = append(xxh, d[1]+":"+d[2])
			}
		}
	} else {
		sqlquery := "SELECT t2.agent_tcp_ip,t1.service_port FROM service_info t1,agent_info t2 WHERE t1.agent_id=t2.agent_id AND t1.app_id=(SELECT id FROM application WHERE app_name='" + key + "') AND t1.service_type=(SELECT id FROM service_type WHERE service_type='" + service + "');"

		resp, err = http.PostForm(InmQuery, url.Values{"sql": {sqlquery}})
		if err != nil {
			fmt.Println(err)
		}
		defer resp.Body.Close()
		body, _ := ioutil.ReadAll(resp.Body)
		xxh = Jsoninfo(body)
	}
	return xxh
}

func JudeHost(definehost, defaulthost, port string) []string {
	var xhst []string
	if definehost != "" {
		hst := strings.Split(definehost, ",")
		for _, hs := range hst {
			if !strings.Contains(hs, "192.168.") {
				xhst = append(xhst, "192.168."+hs+":"+port)
			} else {
				xhst = append(xhst, hs+":"+port)
			}
		}
	} else {
		xhst = []string{"127.0.0.1:" + port}
	}
	return xhst
}

func JudePort(defineport, defaultport string) string {
	if defineport != "" {
		return defineport
	} else {
		return defaultport
	}
}

func ClsRedis(xhost, xkey []string) {
	for _, key := range xkey {
		for _, host := range xhost {
			ip := strings.Split(host, ":")[0]
			xport := strings.Split(host, ":")[1]
			client := redis.New()
			port, _ := strconv.Atoi(xport)
			T_out := time.Duration(time.Second * 10)
			err := client.ConnectWithTimeout(ip, uint(port), T_out)
			if err != nil {
				fmt.Println("connect redis-server failure")
				return
			}
			n, err := client.Del(key)
			if err != nil {
				log.Printf("INFO:Del fail %s,ip=%s,n=%d\n", key, host, n)
			} else {
				log.Printf("INFO:Del success %s,ip=%s,n=%d\n", key, host, n)
			}
		}
	}
}
func ClsSquid(xhost, tturl []string) {
	for _, turl := range tturl {
		for _, host := range xhost {
			i, err := url.Parse(turl)
			if err != nil {
				return
			}
			uri := i.RequestURI()
			domain := i.Host
			hcsurl := "http://" + host + uri
			rep, _ := ReqUrl(hcsurl, "PURGE", domain)
			if err != nil {
				return
			}
			switch rep.StatusCode {
			case 200:
				log.Printf("INFO:DelSuccess %s,ip=%s\n", turl, host)
			case 404:
				log.Printf("INFO:NotFound %s,ip=%s\n", turl, host)
			default:
				log.Printf("ERR:Failure %s,ip=%s\n", turl, host)
			}
		}
	}
}

func Help() {
	color.Red("Version: xcachetools_v1.0\n")
	color.Red("Author:kang\n")
	color.Green("Function description: clean cache server special key\n")
	color.Cyan("========parameter details========\n")
	color.Cyan("-h :   show help info\n")
	color.Cyan("-t :   service type eg: -t=n,t=a,t=m\n")
	color.Cyan("-f :   url list files eg: -f=url.list\n")
	color.Cyan("-i :   cache server ip eg: -i=\"2xx.43,2xx.44\"\n")
	color.Cyan("-p :   cache server port eg: -p=8080\n")
	color.Cyan("-n :   query service ip eg: -n=bbs.pcauto.com.cn\n")
	color.Cyan("-a :   enable or disable auto query service ip and clean cache eg: -a\n")
	color.Red("==Usage: document== \n")
	fmt.Printf("Sevice Type(Default Port)  :  Usage\n")
	fmt.Printf("TrafficServer(8080)        :  [1]xcachetools -t=trafficserver|ats|a -i=\"239.43,239.44\" url\n")
	fmt.Printf("                           :  [2]xcachetools -t=trafficserver|ats|a -i=\"239.43,239.44\" -p=9090 url\n")
	fmt.Printf("                           :  [3]xcachetools -t=trafficserver|ats|a -a url\n")
	fmt.Printf("                           :  [4]xcachetools -t=trafficserver|ats|a -n=bbs.pcauto.com.cn -a -f=url.list\n")
	fmt.Printf("Squid(8001)                :  xcachetools -t=squid|s -i=\"239.43,239.44\" -p=9090 url\n")
	fmt.Printf("nginx|tengine(8010)        :  xcachetools -t=ngx|nginx|tengine|n -i=\"239.43,239.44\" -p=9090 url\n")
	fmt.Printf("HCS(9090)                  :  xcachetools -t=hcs|h -i=\"239.43,239.44\" -p=9090 url\n")
	fmt.Printf("Memcached(11211)           :  xcachetools -t=memcache|mc|m -i=\"239.43,239.44\" -p=11212 key1\n")
	fmt.Printf("Redis(6379)                :  xcachetools -t=redis|rs|r -i=\"239.43,239.44\"\n")
	color.Red("==CDN Usage document== \n")
	fmt.Printf("CDN|wangsu                 :  [1]xcachetools -t=ws|w url\n")
	fmt.Printf("                           :  [2]xcachetools -t=ws|w -f=url.list\n")
	fmt.Printf("CDN|dilian                 :  xcachetools -t=dl|d url\n")
	fmt.Printf("CDN|dilian2                :  xcachetools -t=dl2|d2 url\n")
}

func main() {
	help := flag.Bool("h", false, "show help")
	xtype := flag.String("t", "", "clear cache server type")
	files := flag.String("f", "", "URL list")
	xhost := flag.String("i", "", "cache server ip")
	isautoip := flag.Bool("a", false, "get from inm ip")
	appinfo := flag.String("n", "", "appname or domain")
	showColor := flag.Bool("c", true, "show or not show color")
	xport := flag.String("p", "", "cache server port")
	flag.Parse()
	if *showColor {
		color.Enable()
	}

	if *help || len(flag.Args()) == 0 && *xtype == "" {
		Help()
		return
	}

	var domain string
	//if len(flag.Args()) > 0 {
	//	if strings.Contains(flag.Args()[0], "http://") {
	//		i, err := url.Parse(strings.Split(flag.Args()[0], ",")[0])
	//		if err != nil {
	//			return
	//		}
	//		domain = i.Host
	//	}
	//}

	//if *isautoip {
	//	fmt.Println(GetAppIp("bbs.pcauto.com.cn", "tengine"))
	//	return
	//}

	var xkey []string
	if *files != "" {
		xkey = ReadFileList(*files)
		domain = *appinfo
	} else {
		if len(flag.Args()) > 0 {
			xkey = strings.Split(flag.Args()[0], "|")
			if strings.Contains(flag.Args()[0], "http://") {
				i, err := url.Parse(strings.Split(flag.Args()[0], ",")[0])
				if err != nil {
					return
				}
				domain = i.Host
			}
		}
	}

	var th []string
	switch *xtype {
	case "ats", "trafficserver", "a":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "trafficserver")))
		//	return
		//}
		xp := JudePort(*xport, "8080")
		if *isautoip {
			th = GetAppIp(domain, "trafficserver")
		} else {
			th = JudeHost(*xhost, "127.0.0.1", xp)
		}
		ClsAts(th, xkey)
	case "ngx", "nginx", "tengine", "n", "tng":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "nginx")))
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "tengine")))
		//	return
		//}
		xp := JudePort(*xport, "8010")
		if *isautoip {
			th = GetAppIp(domain, "tengine")
		} else {
			th = JudeHost(*xhost, "127.0.0.1", xp)
		}
		ClsNgx(th, xkey)
	case "hcs", "h":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "hcs")))
		//	return
		//}
		xp := JudePort(*xport, "9090")
		if *isautoip {
			th = GetAppIp(domain, "hcs")
		} else {
			th = JudeHost(*xhost, "127.0.0.1", xp)
		}
		ClsHcs(th, xkey)
	case "mc", "memcache", "m":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "memcache")))
		//	return
		//}
		xp := JudePort(*xport, "11211")
		th := JudeHost(*xhost, "127.0.0.1", xp)
		ClsMc(th, xkey)
	case "squid", "s":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "squid")))
		//	return
		//}
		xp := JudePort(*xport, "8001")
		if *isautoip {
			th = GetAppIp(domain, "hcs")
		} else {
			th = JudeHost(*xhost, "127.0.0.1", xp)
		}
		ClsSquid(th, xkey)
	case "redis", "r", "rs":
		//if *appinfo != "" {
		//	fmt.Println(RemoveDuplicatesAndEmpty(GetAppIp(*appinfo, "redis")))
		//	return
		//}
		xp := JudePort(*xport, "6379")
		th := JudeHost(*xhost, "127.0.0.1", xp)
		ClsRedis(th, xkey)
	case "dilian", "dl", "d":
		ClsDiLian(ProxyIp, DILianUrl, DiLianToken, xkey)
	case "dilian2", "dl2", "d2":
		ClsDiLian2(ProxyIp, DLPushUrl, DLCaptcha, xkey)
	case "wangsu", "ws", "w":
		ClsWangSu(ProxyIp, WsUrl, WsUser, WsPass, xkey)
	default:
		fmt.Println(*xtype + "not support")
	}

}
